# Created by Gao Song on 2019-04-09
import logging
import time
from datetime import datetime
from enum import Enum

import pandas as pd
from pandas import Series
from rqdatac.services.future import get_dominant

from fucker.order_management import OrderManagement
from fucker.pos_management import PosManagement
from lqu import future_contract_is_night_trading

logger = logging.getLogger('fucker')

from core.constant import (
    Direction,
    OrderType,
    Exchange,
    Offset,
    Status as OrderStatus
)
from core.object import (
    OrderRequest,
    SubscribeRequest, )
from event import Event
from event.event_def import (
    EVENT_TICK,
    EVENT_ORDER,
    EVENT_TRADE,
    EVENT_POSITION,
    EVENT_ALL_POSITIONS,
    EVENT_CANCEL_ORDER_REJECTED, EVENT_ORDER_ERROR
)
from gateway.base_gateway import BaseGateway


class FuckerStatus(Enum):
    NOT_INITED = '未初始化'
    READY = '就绪'
    SET_TASK = '设置任务'
    RUNNING = '正在运行'
    PAUSED = '暂停'
    FINISHED = '已完成'
    TERMINATED = '中断'


class BaseFucker:
    def __init__(self, gateway: BaseGateway, **kwargs):
        self.event_engine = gateway.event_engine

        self.gateway = gateway

        self.order_finished = {}
        self.order_unfinished = {}
        self.orders_unknown = {}
        self.orders_own = set()

        self.cancelling_order_id = set()

        self.position_management = PosManagement()
        # 订单的相关信息
        self.order_management = OrderManagement(**kwargs)

        self.register_events()

        self.target_pos = None
        self.change_pos = None
        self.finished = None
        self.status = FuckerStatus.NOT_INITED

        self.got_all_positions = False

        self.fuck_class_name = self.__class__.__qualname__

    def register_events(self):
        self.event_engine.register(EVENT_POSITION, self.on_position)
        self.event_engine.register(EVENT_ORDER, self.on_order)
        self.event_engine.register(EVENT_TICK, self.on_tick)
        self.event_engine.register(EVENT_TRADE, self.on_trade)
        self.event_engine.register(EVENT_ALL_POSITIONS, self.on_all_positions)
        self.event_engine.register(EVENT_CANCEL_ORDER_REJECTED, self.on_cancel_order_rejected)
        self.event_engine.register(EVENT_ORDER_ERROR, self.on_order_error)

    def unregister_events(self):
        self.event_engine.unregister(EVENT_POSITION, self.on_position)
        self.event_engine.unregister(EVENT_ORDER, self.on_order)
        self.event_engine.unregister(EVENT_TICK, self.on_tick)
        self.event_engine.unregister(EVENT_TRADE, self.on_trade)
        self.event_engine.unregister(EVENT_ALL_POSITIONS, self.on_all_positions)
        self.event_engine.unregister(EVENT_CANCEL_ORDER_REJECTED, self.on_cancel_order_rejected)
        self.event_engine.unregister(EVENT_ORDER_ERROR, self.on_order_error)

    def init(self, time_out=120):
        time.sleep(10)
        wait_time = 0
        while not self.gateway.is_ready():
            wait_time += 5
            if wait_time > time_out:
                logger.warning('gateway not ready in fucker init')
                self.gateway.init_query()
                return
            time.sleep(5)
        time.sleep(5)
        self.gateway.query_position()
        time.sleep(10)

        # TODO how to make sure pos is got...
        wait_time = 0
        while not self.got_all_positions:
            wait_time += 5
            if wait_time > time_out:
                logger.warning('no positions in fucker init')
                self.gateway.query_position()
                return
            time.sleep(5)

        logger.info('borker pos:')
        for symbol, pos in self.position_management.holdings.items():
            logger.info(
                [symbol, pos.long_pos, pos.long_td, pos.long_yd, pos.short_pos, pos.short_td, pos.short_yd])

        # TODO cancel orders?
        # self.cancel_unknown_orders()
        self.status = FuckerStatus.READY

    def on_all_positions(self, e):
        self.got_all_positions = True
        # self.status = FuckerStatus.READY

    def on_cancel_order_rejected(self, e):
        data = e.data
        orderid = data['orderid']
        self.gateway.query_order()

    def set_task(self, target_pos: Series, **kwargs):
        if self.status != FuckerStatus.READY:
            return
        self.target_pos = target_pos
        current_pos = self.position_management.to_series()
        # target_error_limit 操作
        self.target_pos = self.target_error_limit_pos(self.target_pos, current_pos, **kwargs)

        self.change_pos = self.target_pos.sub(current_pos, fill_value=0)

        logger.info(f'change_pos:{self.change_pos}')

        if datetime.now().hour > 16:
            # 没有夜盘合约列表
            self.not_night_trading_list = []
            for sym in self.change_pos.index:
                if not future_contract_is_night_trading(sym):
                    self.not_night_trading_list.append(sym)

            logger.info(f'night trade, delay {self.not_night_trading_list}')
            self.change_pos.drop(self.not_night_trading_list, inplace=True)


        is_finished = []
        for sym in self.change_pos.index:
            if self.is_finished(sym):
                is_finished.append(sym)

        logger.info(f'remove finished {is_finished}')
        self.change_pos.drop(is_finished, inplace=True)

        logger.info(f'drop change_pos :{self.change_pos}')
        # 特殊账户分数交易时间表
        # 只有fucker_type=smart 时才进行时间表确认
        if self.fuck_class_name == 'SmartFucker':
            self.trade_time_table = self._set_trade_time_table(**kwargs)
            logger.info(f'trade_time_table::{self.trade_time_table}')

        self.subscribe_symbols(self.change_pos.index)
        self.finished = Series(False, index=self.change_pos.index)
        self.status = FuckerStatus.SET_TASK


    def _set_account_time(self, trade_time_score_df, **kwargs):
        def _set_value(data, col):
            if data is not None:
                for key, value in data.items():
                    # 获取主力
                    dominant = get_dominant(key)
                    trade_time_score_df.loc[dominant, col] = value

        # 读取标的自定义配置
        _set_value(kwargs.get('symbols_start_time_morning'), 'start_time_morning')
        _set_value(kwargs.get('symbols_start_time_afternoon'), 'start_time_afternoon')
        _set_value(kwargs.get('symbols_start_time_night'), 'start_time_night')

        return trade_time_score_df

    def target_error_limit_pos(self, target_pos, current_pos, **kwargs):
        """
        目标标的市值和持有标的市值与总资产进行比较。比例<0.01时,保留原有持仓。
        举例：现在总资产是2kW
        target_error_limit阈值是0.01
        相当于目标市值和柜台市值相差在20W以内。仓位保持不变。
        """

        # logger.debug(f'进入误差流程，\n target_pos：{target_pos} \n current_pos:{current_pos},\n kwargs:{kwargs}')

        contract_multiplier = kwargs.get('contract_multiplier')
        price = kwargs.get('price')
        # 误差值
        target_error_limit = kwargs.get('target_error_limit')
        # 总资产
        nav = kwargs.get('nav')
        # 不需要处理。返回
        if (target_error_limit is None or contract_multiplier is None or price is None):
            logger.debug(f'没有读取到参数，不需要改变')
            return target_pos

        # 标的目标市值
        target_turnover = pd.Series()
        for symbol in target_pos.keys():
            target_turnover[symbol] = abs(target_pos[symbol] * price[symbol] * contract_multiplier[symbol])

        # 标的持仓市值，如果是移仓换月的情况。在target持仓中不存在该合约。price , contract_multiplier中也没有这个合约。默认值为0，后续不参与计算
        current_turnover = pd.Series()
        for symbol in current_pos.keys():
            turnover = abs(current_pos[symbol] * price.get(symbol, 0) * contract_multiplier.get(symbol, 0))
            if turnover > 0:
                current_turnover[symbol] = turnover

        turnover_df = pd.concat([target_turnover, current_turnover], keys=['target_turnover', 'current_turnover'],
                                axis=1)
        # logger.debug(f'标的市值:\n{turnover_df}')
        turnover_df_tmp = (((turnover_df['target_turnover'] - turnover_df['current_turnover']) / nav)).dropna().abs()
        # logger.debug(f'标的市值变化情况。取绝对值（target_turnover-current_turnover）/nav :\n{turnover_df_tmp}')
        # 过滤变化<target_error_limit 标的
        change_res = turnover_df_tmp[(turnover_df_tmp > 0) & (turnover_df_tmp < target_error_limit)]
        # 没有小于阈值的标的
        if change_res.empty:
            # logger.debug(f'没有在阈值范围内的标的。target_error_limit：{target_error_limit}')
            return target_pos

        log_list = []
        for symbol, value in change_res.items():
            # logger.debug(f'target_error_limit . 存在阈值检查标的:{symbol},(broker/target-1).abs 市值比值{value}')

            # 方向相反的情况，不做操作
            if target_pos[symbol] * current_pos[symbol] < 0:
                # logger.debug(f'目标标的{target_pos[symbol]}和柜台标的{current_pos[symbol]}方向相反。使用目标标的')
                continue
            # 标的目标手数使用持仓手数
            ori_target_pos = target_pos[symbol]
            target_pos[symbol] = current_pos[symbol]
            change_dict = {'gateway_name': self.gateway.gateway_name, 'symbol': symbol,
                           'target_error_limit': f'{target_error_limit}',
                           'nav': nav,
                           'current_pos': f'{current_pos[symbol]}',
                           'current_turnover': f'{current_turnover[symbol]}',
                           'target_pos': f'{ori_target_pos}',
                           'target_turnover': f'{target_turnover[symbol]}'}
            log_list.append(change_dict)



        # logger.debug(f'进入误差流程，\n target_pos：{target_pos} ')

        return target_pos

    def start(self, at: datetime = None):
        if self.change_pos is None or len(self.change_pos) == 0:
            logger.info('nothing changed. exit.')
            self._stop()
        else:
            if at is not None:
                time.sleep((at - datetime.now()).seconds)
            self.status = FuckerStatus.RUNNING

    def pause(self):
        if self.status == FuckerStatus.RUNNING:
            self.status = FuckerStatus.PAUSED

    def resume(self):
        if self.status == FuckerStatus.PAUSED:
            self.status = FuckerStatus.RUNNING

    def _stop(self):
        self.status = FuckerStatus.FINISHED
        # self.unregister_events()
        logger.info('fucker stopped.')

    def terminate(self):
        if not self.status in (FuckerStatus.FINISHED, FuckerStatus.TERMINATED):
            self.cancel_all_orders()
            self.status = FuckerStatus.TERMINATED
            logger.info('fucker terminated.')

    def subscribe_symbols(self, vt_symbols):
        for vt_symbol in vt_symbols:
            logger.info(f'subscribe {vt_symbol}')
            symbol, exchange = vt_symbol.split('.')
            exchange = Exchange(exchange)
            req = SubscribeRequest(symbol=symbol, exchange=exchange)
            self.gateway.subscribe(req)

    def on_tick(self, e: Event):
        pass

    def on_trade(self, e: Event):
        if self.status == FuckerStatus.NOT_INITED:
            return

        trade = e.data

        self.position_management.update_trade(trade)
        logger.info(trade)

        if self.status != FuckerStatus.RUNNING:
            return

        self.check_finished(trade.vt_symbol)

    def check_finished(self, vt_symbol):
        if self.is_finished(vt_symbol):
            self.end_symbol(vt_symbol)

            if all(self.finished):
                logger.info('all finished.')
                self._stop()

    def end_symbol(self, vt_symbol):
        logger.info(f'{vt_symbol} is finished.')
        self.finished[vt_symbol] = True
        symbol, exchange = vt_symbol.split('.')
        exchange = Exchange(exchange)
        req = SubscribeRequest(symbol=symbol, exchange=exchange)
        self.gateway.unsubscribe(req)

    def on_position(self, e: Event):
        position = e.data
        self.position_management.update_position(position)

    def on_order(self, e: Event):

        if self.status == FuckerStatus.NOT_INITED:
            return

        order = e.data
        logging.info(order)

        if order.status == OrderStatus.CANCELLED and order.vt_orderid in self.cancelling_order_id:
            self.cancelling_order_id.remove(order.vt_orderid)

        self.position_management.update_order(order)

        if order.vt_orderid not in self.orders_own:
            if order.is_active():
                self.orders_unknown[order.vt_orderid] = order
            return

        if order.vt_orderid not in self.order_unfinished.keys():
            self.order_unfinished[order.vt_orderid] = order

        if not order.is_active():
            del self.order_unfinished[order.vt_orderid]
            self.order_finished[order.vt_orderid] = order

        if order.status == OrderStatus.CANCELLED:
            self.check_finished(order.vt_symbol)

    def on_order_error(self, e: Event):
        orderDataError = e.data
        error_id = orderDataError.error_id
        vt_symbol = orderDataError.vt_symbol
        logger.debug(f'on_order_error error_id::{error_id},{type(error_id)}, vt_symbol:: {vt_symbol}')
        if self.status == FuckerStatus.NOT_INITED:
            return
        if 20 == error_id:
            logger.debug(f'running error_id::{error_id}, vt_symbol:: {vt_symbol}')
            current_pos = self.position_management.get_position_holding(vt_symbol).current_pos
            if current_pos == 0:
                self.target_pos[vt_symbol] = 0
                self.check_finished(vt_symbol)
                logger.info(f'vt_symbol:{vt_symbol} error, error_info:{error_id},{orderDataError.error_msg}')

    def send_order(
            self,
            symbol: str,
            exchange: Exchange,
            direction: Direction,
            offset: Offset,
            price: float,
            volume: float,
            order_type: OrderType,
            lock: bool
    ):

        if self.status != FuckerStatus.RUNNING:
            return

        vt_symbol = f'{symbol}.{exchange.value}'
        min_vol_limit_bool, volume = self.order_management.check_order(vt_symbol, offset, volume)
        if min_vol_limit_bool:
            logger.info(f'由于最小开仓限制不能发送订单：{vt_symbol}/{direction}/{offset}/{volume}')
            return

        if volume < 0:
            logger.info(f'开仓手数必须大于0：{vt_symbol}/{direction}/{offset}/{volume}')
            return

        # Create request and send order.
        original_req = OrderRequest(
            symbol=symbol,
            exchange=exchange,
            direction=direction,
            offset=offset,
            type=order_type,
            price=price,
            volume=volume,
        )

        # Convert with offset converter
        req_list = self.position_management.convert_order_request(original_req, lock)

        # Send Orders
        vt_orderids = []

        for req in req_list:
            vt_orderid = self.gateway.send_order(req)
            vt_orderids.append(vt_orderid)
            self.orders_own.add(vt_orderid)
            self.position_management.update_order_request(req, vt_orderid)

        return vt_orderids

    def send_net_pos_order(self, vt_symbol, price, pos):
        symbol, exchange = vt_symbol.split('.')
        exchange = Exchange(exchange)
        lock = False
        current_pos = self.position_management.get_position_holding(vt_symbol).current_pos
        if pos > 0 and current_pos >= 0:
            self.send_order(symbol, exchange, Direction.LONG, Offset.OPEN, price, abs(pos), OrderType.LIMIT, lock)
        elif pos < 0 and current_pos <= 0:
            self.send_order(symbol, exchange, Direction.SHORT, Offset.OPEN, price, abs(pos), OrderType.LIMIT, lock)
        elif pos > 0 and abs(pos) <= abs(current_pos):
            self.send_order(symbol, exchange, Direction.LONG, Offset.CLOSE, price, abs(pos), OrderType.LIMIT, lock)
        elif pos < 0 and abs(pos) <= abs(current_pos):
            self.send_order(symbol, exchange, Direction.SHORT, Offset.CLOSE, price, abs(pos), OrderType.LIMIT, lock)
        elif pos > 0 and abs(pos) > abs(current_pos):
            self.send_order(symbol, exchange, Direction.LONG, Offset.CLOSE, price, abs(current_pos), OrderType.LIMIT,
                            lock)
            self.send_order(symbol, exchange, Direction.LONG, Offset.OPEN, price, abs(pos + current_pos),
                            OrderType.LIMIT, lock)
        elif pos < 0 and abs(pos) > abs(current_pos):
            self.send_order(symbol, exchange, Direction.SHORT, Offset.CLOSE, price, abs(current_pos), OrderType.LIMIT,
                            lock)
            self.send_order(symbol, exchange, Direction.SHORT, Offset.OPEN, price, abs((pos + current_pos)),
                            OrderType.LIMIT, lock)

    def cancel_order(self, order):
        if order.vt_orderid not in self.cancelling_order_id and order.is_active():
            self.cancelling_order_id.add(order.vt_orderid)
            req = order.create_cancel_request()
            self.gateway.cancel_order(req)

    def cancel_all_orders(self):
        for order in self.order_unfinished.values():
            req = order.create_cancel_request()
            self.gateway.cancel_order(req)

    def cancel_unknown_orders(self):
        logger.debug('cancel unknown orders...')
        for order in self.orders_unknown.values():
            if order.is_active():
                logger.info(order.vt_orderid)
                req = order.create_cancel_request()
                self.gateway.cancel_order(req)

    def is_finished(self, vt_symbol):
        holding = self.position_management.get_position_holding(vt_symbol)
        current_pos = holding.current_pos
        trading_pos = holding.trading_pos
        target_pos = self.target_pos.get(vt_symbol, 0)

        logger.debug(
            f'vt_symbol:{vt_symbol},current_pos:{current_pos},trading_pos:{trading_pos},target_pos：{target_pos}')

        # 还有正在交易的
        if trading_pos != 0:
            return False
        # 正常交易完成
        if current_pos == target_pos:
            return True

        # 判断特殊品种。
        if vt_symbol not in self.order_management.once_min_open_vol_limit:
            return False
        # 特殊品种交易完成判断条件。。目标持仓和柜台持仓必须是同一方向，目标>柜台，目标<特殊限制数量
        if (target_pos * current_pos) >= 0 and abs(target_pos) >= abs(current_pos) and \
                (abs(target_pos) - abs(current_pos)) < self.order_management.once_min_open_vol_limit[vt_symbol]:
            logger.info(
                f'min_open_vol_limit finished vt_symbol:{vt_symbol},current_pos:{current_pos},trading_pos:{trading_pos},target_pos:{target_pos}')
            return True

        return False
